﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.WebSockets;
using Antlr.Runtime;
using Charades.Web.Helpers;
using Charades.Web.Hubs;
using Charades.Web.Server.GenericManagers;
using Charades.Web.Server.Room;
using Charades.Web.Server.User;
using Elmah;
using Microsoft.AspNet.SignalR;

namespace Charades.Web.Server
{
	using System.Collections.Concurrent;
	using System.Configuration;
	using System.Threading;
	using System.Threading.Tasks;
	using System.Timers;

	using Charades.Web.Server.Round;

	public class Game
	{
		private readonly static Lazy<Game> _instance = new Lazy<Game>(() => new Game());
		private readonly int MinRoomSize = int.Parse(ConfigurationManager.AppSettings.Get("MinRoomSize"));
		private readonly int RoundsCount = int.Parse(ConfigurationManager.AppSettings.Get("RoundsCount"));
		private readonly string TargetFolder = HostingEnvironment.MapPath("~/pic");
		private object _locker = new object();

		private Game()
		{
			UserHandler = new UserHandler();
			RoomHandler = new RoomHandler(UserHandler, _locker);
			ConnectionManager = new ConnectionManager(UserHandler, RoomHandler, _locker);
		}

		public UserHandler UserHandler { get; private set; }
		public RoomHandler RoomHandler { get; private set; }
		public ConnectionManager ConnectionManager { get; private set; }

		/// <summary>
		/// Контекст SignalR
		/// </summary>
		/// <returns></returns>
		public static IHubContext GetContext()
		{
			return GlobalHost.ConnectionManager.GetHubContext<GameHub>();
		}

		/// <summary>
		/// инстанс класса Game
		/// </summary>
		public static Game Instance
		{
			get { return _instance.Value; }
		}

		/// <summary>
		/// инициализация новго пользователя
		/// </summary>
		/// <param name="connectionId">connection Id пользователя</param>
		/// <param name="name">имя пользователя</param>
		/// <returns></returns>
		public object InitializeUser(string connectionId, string name)
		{
			if (!UserHandler.UserExists(connectionId))
			{
				try
				{
					lock (_locker) // возможно лишний lock
					{
						var user = new User.User(connectionId, name);
						UserHandler.AddUser(user);
						return new { name = user.Name, color = user.Color };
					}
				}
				catch (Exception e)
				{
					////ErrorLog.Instance.Log(e);
				}
			}

			return null;
		}

		/// <summary>
		/// поиск комнаты
		/// </summary>
		/// <param name="connectionId">connection Id пользователя</param>
		public void FindRoom(string connectionId)
		{
			try
			{
				lock (_locker)
				{
					var room = RoomHandler.FindRoomForNewUser();
					var user = UserHandler.GetUser(connectionId);
					var context = GetContext();

					room.AddUser(user);
					context.Groups.Add(connectionId, room.Id);

					// респонс для пользователей в комнате
					var response1 = new
									{
										user = user
									};

					context.Clients.Group(room.Id, connectionId).userAdded(response1);

					// репосн для нового пользователя
					var response2 = new
									{
										users = room.Users
									};

					context.Clients.Client(connectionId).roomFound(response2);

					// проверяется готовность комнаты
					if (room.Users.Count == MinRoomSize)
					{
						// запросы шлються по отдельности потому что пользователь еще не добавился в SignalR группу этой комнаты
						room.Status = RoomStatus.Started;
						context.Clients.Group(room.Id).startGame();
						context.Clients.Client(connectionId).startGame();
						StartNewRound(room);
						RoomCreatingTimer.StopAndGetSpan();
						
					}
					else if (room.Status == RoomStatus.Started)
					{
						// если комната уже стартанула
						context.Clients.Client(connectionId).startGame(room.Rounds.OrderBy(r => r.CreatingDate).Last().State == RoundState.Started);
					}
				}
			}
			catch (Exception e)
			{
				////ErrorLog.Instance.Log(e);
			}
		}

		/// <summary>
		/// Начать новый раунд в комнате
		/// </summary>
		/// <param name="room">Комната</param>
		private void StartNewRound(Room.Room room)
		{
			lock (_locker)
			{
				if (room.Users.Count == 0 || room.Status == RoomStatus.Finished) return;

				//var user = room.Users[room.Rounds.Count % room.Users.Count]; //magic!
				// так как теперь пользователь может входить в комнату во время игры магия не работает, просто много кода
				var lastRound = room.Rounds.OrderBy(r => r.CreatingDate).LastOrDefault();
				int index = lastRound == null ? -1 : room.Users.IndexOf(lastRound.User);
				index++;
				var user = index >= room.Users.Count ? room.Users.First() : room.Users[index];

				// создается новый раунд
				var newRound = new Round.Round() { Words = WordsHelper.GetWords(), User = user };
				room.Rounds.Add(newRound);

				// запрос на выбор слова
				GetContext().Clients.Client(user.ConnectionId).chooseWord(newRound.Words);

				// если через 15 секунд раунд не изменит статус пропускаем этот раунд
				TimerHelper.CreateTimer(15000,
					(sender, eventArg) =>
						{
							lock (_locker)
							{
								if (newRound.State == RoundState.Default)
								{
									var t = new Task(() => this.StartRound(user.ConnectionId, -1));
									t.Start();
								}
							}
						});
			}
		}

		/// <summary>
		/// Пользователь выбрал слово и стартует раунд
		/// </summary>
		/// <param name="connectionId">Connection Id пользователя</param>
		/// <param name="wordId">ID слова</param>
		public void StartRound(string connectionId, int wordId)
		{
			var room = RoomHandler.GetRoom(connectionId);
			var round = room.Rounds.FirstOrDefault(r => r.User.ConnectionId == connectionId && r.State == RoundState.Default);
			if (round != null)
			{
				// если слово выбрано стартуем таймер, иначе предлагаем выбрать слово след. пользователю
				if (wordId >= 0 && wordId < round.Words.Length)
				{
					round.Word = round.Words[wordId];
					round.State = RoundState.Started;
					StartRoundTimer(room, round);
					GetContext().Clients.Group(room.Id, connectionId).startTimer(new { name = round.User.Name });
				}
				else
				{
					round.State = RoundState.Skipped;
					StartNewRound(room);
				}
			}
		}

		/// <summary>
		/// Входящее сообщение
		/// </summary>
		/// <param name="connectionId">Connection Id пользователя</param>
		/// <param name="msg">Текст сообщения</param>
		public void SendMessage(string connectionId, string msg)
		{
			lock (_locker)
			{
				// отправляем сообщение всей комнате
				var user = UserHandler.GetUser(connectionId);
				GetContext().Clients.Group(user.RoomId).newMessage(new { name = user.Name, color = user.Color, msg = msg });

				// проверяем ответ
				var room = RoomHandler.GetRoom(connectionId);
				var round = room.Rounds.FirstOrDefault(r => r.State == RoundState.Started && r.User.ConnectionId != connectionId);

				// проверка слова
				if (round != null && msg.ToLower().Replace('ё', 'е').Replace('й', 'и').Split(' ').Contains(round.Word.ToLower().Replace('ё', 'е').Replace('й', 'и')))
				{
					round.State = RoundState.Finished;
					round.User.Points += 15;
					user.Points += round.AboutTime == 0 ? 20 : 100 / round.AboutTime;

					// останавливается раунд
					GetContext().Clients.Group(room.Id).stopRound(new
																  {
																	  timerOver = false,
																	  painter = round.User.Name,
																	  word = round.Word,
																	  winner = user.Name,
																	  users = room.Users.OrderByDescending(u => u.Points),
																	  roundCount = room.Rounds.Count(r => r.State == RoundState.Finished)
																  });

					// формируется пустой файл картинки
					var fileName = RandomNameHelper.GetName() + Base32Hepler.EncodeTo32(round.User.Name + " " + round.Word);
					var targetPath = Path.Combine(TargetFolder, fileName + ".png");
					File.Create(targetPath).Dispose();

					// запрос на загрузку картинки
					GetContext().Clients.Client(round.User.ConnectionId).getImage(new { word = round.Word, painter = round.User.Name, name = fileName });

					// или заканчиваем игру или начинаем следующий раунд
					if (room.Rounds.Count(r => r.State == RoundState.Finished) >= RoundsCount)
					{
						room.Status = RoomStatus.Finished;
						var winners = room.Users.Where(u => u.Points == room.Users.Max(u2 => u2.Points)).Select(u => u.ConnectionId).ToArray();
						GetContext().Clients.Group(room.Id, winners).stopGame(new { win = false });
						GetContext().Clients.Clients(winners).stopGame(new { win = true });
					}
					else
					{
						TimerHelper.CreateTimer(10000, (s2, e2) => StartNewRound(room));
					}
				}
			}
		}

		/// <summary>
		/// Откллючание пользователя
		/// </summary>
		/// <param name="connectionId">Connection Id пользователя</param>
		public void OnDisconnected(string connectionId)
		{
			lock (_locker)
			{
				var user = UserHandler.GetUser(connectionId);
				if (user != null)
				{
					// вызываем событие отключения пользователя на клиенте
					GetContext().Clients.Group(user.RoomId, connectionId).userGotOut(user);
					var room = RoomHandler.GetRoom(connectionId);

					if (room == null) { return; }

					// проверка людей оставшихся в комнате
					if (room.Users.Count <= MinRoomSize)// <= потому что данный пользователь еще не удален
					{
						// вызываем закрытие комнаты
						GetContext().Clients.Group(room.Id).roomClosed();
					}
					else
					{
						// получаем поледний раунд и проверяем не этот ли пользователь отключился
						var lastRound = room.Rounds.OrderBy(r => r.CreatingDate).LastOrDefault();
						if (lastRound != null && lastRound.User.ConnectionId == connectionId && lastRound.State != RoundState.Finished)
						{
							lastRound.State = lastRound.State == RoundState.Started ? RoundState.Finished : RoundState.Skipped;
							GetContext().Clients.Group(room.Id).stopRound(new
																			{
																				timerOver = false,
																				painter = lastRound.User.Name,
																				word = lastRound.Word,
																				winner = string.Empty,
																				users = room.Users.OrderByDescending(u => u.Points),
																				roundCount = room.Rounds.Count(r => r.State == RoundState.Finished)
																			});

							StartNewRound(room);
						}
					}
					// событие дисконекшина ConnectionManager
					ConnectionManager.OnDisconnected(user);
				}
			}
		}

		/// <summary>
		/// Стартует таймеры для раунда
		/// </summary>
		/// <param name="room"></param>
		/// <param name="round"></param>
		public void StartRoundTimer(Room.Room room, Round.Round round)
		{
			// время вышло
			TimerHelper.CreateTimer(
				60000,
				(e, s) =>
				{
					if (round.State == RoundState.Started && room.Status == RoomStatus.Started)
					{
						round.State = RoundState.Finished;
						// отправляется сообщение об окончании раунда
						GetContext().Clients.Group(room.Id).stopRound(new
																	  {
																		  timerOver = true,
																		  painter = round.User.Name,
																		  word = round.Word,
																		  winner = string.Empty,
																		  users = room.Users.OrderByDescending(u => u.Points),
																		  roundCount = room.Rounds.Count(r => r.State == RoundState.Finished)
																	  });

						// создание пустого файла
						var fileName = RandomNameHelper.GetName() + Base32Hepler.EncodeTo32(round.User.Name + " " + round.Word);
						var targetPath = Path.Combine(TargetFolder, fileName + ".png");
						File.Create(targetPath).Dispose();

						// запрос на загрузку картинки
						GetContext().Clients.Client(round.User.ConnectionId).getImage(new { word = round.Word, painter = round.User.Name, name = fileName });

						// проверка окончания игры
						if (room.Rounds.Count(r => r.State == RoundState.Finished) >= RoundsCount)
						{
							room.Status = RoomStatus.Finished;
							var winners =
								room.Users.Where(u => u.Points == room.Users.Max(u2 => u2.Points)).Select(u => u.ConnectionId).ToArray();
							GetContext().Clients.Group(room.Id, winners).stopGame(new { win = false });
							GetContext().Clients.Clients(winners).stopGame(new { win = true });
						}
						else
						{
							TimerHelper.CreateTimer(10000, (s2, e2) => StartNewRound(room));
						}
					}
				});

			// прошла первая треть
			TimerHelper.CreateTimer(
				20000,
				(s, e) =>
				{
					if (round.State == RoundState.Started && room.Status == RoomStatus.Started)
					{
						round.AboutTime = 10;
						var str = new StringBuilder();
						for (var i = 0; i < round.Word.Length - 1; i++)
						{
							str.Append("_ ");
						}
						str.Append("_");
						GetContext().Clients.Group(room.Id, round.User.ConnectionId).firstHint(str.ToString());
					}
				});

			// прошла вторая терь
			TimerHelper.CreateTimer(
				40000,
				(s, e) =>
				{
					if (round.State == RoundState.Started && room.Status == RoomStatus.Started)
					{
						round.AboutTime = 20;
						var random = new Random();
						var chars = new int[]{
							                     random.Next(0, round.Word.Length - 1),
							                     random.Next(0, round.Word.Length - 1),
							                     random.Next(0, round.Word.Length - 1)
						                     };

						var str = new StringBuilder();
						for (var i = 0; i < round.Word.Length - 1; i++)
						{
							if (chars.Contains(i))
							{
								str.Append(round.Word[i]);
								str.Append(' ');
							}
							else
							{
								str.Append("_ ");
							}
						}
						str.Append("_");

						GetContext()
							.Clients.Group(room.Id, round.User.ConnectionId)
							.secondHint(str.ToString());
					}
				});
		}
	}
}